package com.gitee.wsl.mathematics.geometry.d2.contour.segment

import com.gitee.wsl.ext.number.clamp
import com.gitee.wsl.ext.number.pow
import com.gitee.wsl.io.dataproxy.asProxy
import com.gitee.wsl.mathematics.coordinate.d2.Coordinate2
import com.gitee.wsl.mathematics.function.Equations.solveCubic
import com.gitee.wsl.mathematics.geometry.d2.AbstractShape
import com.gitee.wsl.mathematics.geometry.d2.base.YPolarity
import com.gitee.wsl.mathematics.geometry.d2.contour.ShapeContour
import com.gitee.wsl.mathematics.geometry.d2.contour.xy01
import com.gitee.wsl.mathematics.geometry.d2.curve.Bezier2
import com.gitee.wsl.mathematics.geometry.d2.curve.CubicBezier2
import com.gitee.wsl.mathematics.geometry.d2.curve.CurveShape2d
import com.gitee.wsl.mathematics.geometry.d2.curve.CurveLine
import com.gitee.wsl.mathematics.geometry.d2.curve.QuadraticBezier2
import com.gitee.wsl.mathematics.geometry.d2.curve.ext.bezier
import com.gitee.wsl.mathematics.geometry.d2.curve.ext.roots
import com.gitee.wsl.mathematics.geometry.d2.curve.ext.safeDerivative
import com.gitee.wsl.mathematics.geometry.d2.rect.ext.bounds
import com.gitee.wsl.mathematics.matrix.mat4.Mat4
import com.gitee.wsl.mathematics.matrix.mat4.ext.times
import com.gitee.wsl.mathematics.vector.ext.distanceTo
import com.gitee.wsl.mathematics.vector.ext.dot
import com.gitee.wsl.mathematics.vector.ext.minus
import com.gitee.wsl.mathematics.vector.vec2.Vec2f
import com.gitee.wsl.mathematics.vector.vec2.Vector2
import com.gitee.wsl.mathematics.vector.vec4.Vec4
import kotlin.jvm.Transient
import kotlin.math.sign
import com.gitee.wsl.mathematics.geometry.d2.curve.ext.derivative2
import com.gitee.wsl.mathematics.matrix.mat4.Mat4f
import com.gitee.wsl.mathematics.vector.ext.cross


class Segment2D(a: Vec2f,
                override val control: List<Vec2f> = mutableListOf(),
                b: Vec2f,
                override val corner: Boolean = false
) : AbstractShape(arrayOf(a,b).asProxy()), CurveSegmentShape2D<Float, Vec2f> {

    fun copy(start: Vec2f=this.start,
             control: List<Vec2f> = this.control,
             end: Vec2f=this.end,
             corner: Boolean = false) = Segment2D(start,control,end,corner)

    override val perimeter: Float get() = TODO()
    override val center: Vec2f get() = (start - end)/2
    override val start: Vec2f get() = proxy[0]
    override val end: Vec2f get() = proxy[1]


    override fun getBounds() = (listOf(start, end) + extremaPoints()).bounds

    override fun create(a: Vec2f, b: Vec2f) = Segment2D(a,b)

    override fun createPoint2(x: Float, y: Float) = Vec2f(x,y)

    @Transient
    private var lut: List<Vec2f>? = null

    @Suppress("unused")
    fun lut(size: Int = 100): List<Vec2f> {
        if (lut == null || lut!!.size != size) {
            lut = (0..size).map { position((it.toFloat() / size)) }
        }
        return lut!!
    }


    fun on(point: Vec2f, error: Double = 5.0): Double? {
        val lut = lut()
        var hits = 0
        var t = 0.0
        for (i in lut.indices) {
            if ((lut[i] - point).squaredLength < error * error) {
                hits++
                t += i.toDouble() / lut.size
            }
        }
        return if (hits > 0) t / hits else null
    }


    override fun tForLength(length: Float): Float {
        if (type == SegmentType.LINEAR) {
            return (length / this.length).coerceIn(0.0f, 1.0f)
        }

        val segmentLength = this.length
        val cLength = length.coerceIn(0.0f, segmentLength)

        if (cLength == 0.0f) {
            return 0.0f
        }
        if (cLength >= segmentLength) {
            return 1.0f
        }
        var summedLength = 0.0f
        lut(100)
        val cLut = lut ?: error("no lut")
        val partitionCount = cLut.size - 1

        val dt = 1.0f / partitionCount
        for ((index, _ /*point*/) in lut!!.withIndex()) {
            if (index < lut!!.size - 1) {
                val p0 = cLut[index]
                val p1 = cLut[index + 1]
                val partitionLength = p0.distanceTo(p1)
                summedLength += partitionLength
                if (summedLength >= length) {
                    val localT = index.toFloat() / partitionCount
                    val overshoot = summedLength - length
                    return localT + (overshoot / partitionLength) * dt
                }
            }
        }
        return 1.0f
    }


    @Suppress("unused")
    private fun closest(points: List<Vec2f>, query: Vec2f): Pair<Int, Vec2f> {
        var closestIndex = 0
        var closestValue = points[0]

        var closestDistance = Float.POSITIVE_INFINITY
        for (i in points.indices) {
            val distance = (points[i] - query).squaredLength
            if (distance < closestDistance) {
                closestIndex = i
                closestValue = points[i]
                closestDistance = distance
            }
        }
        return Pair(closestIndex, closestValue)
    }

    override fun nearestVertex(pt: Vec2f): Vec2f= nearestSegmentPoint(pt).position

    /**
     * Find point on segment nearest to given [point].
     * @param point The query point.
     */
    override fun nearestSegmentPoint(point: Coordinate2<Float, *>): SegmentPoint {
        val t = when (type) {
            SegmentType.LINEAR -> {
                val dir = end - start
                val relativePoint = point - start
                ((dir dot relativePoint) / dir.squaredLength).coerceIn(0.0f, 1.0f)
            }

            SegmentType.QUADRATIC -> {
                val qa = start - point
                val ab = control[0] - start
                val bc = end - control[0]
                val qc = end - point
                val ac = end - start
                val br = start + end - control[0] - control[0]

                var minDistance = sign(ab cross qa) * qa.length
                var param = -(qa dot ab) / (ab dot ab)

                val distance = sign(bc cross qc) * qc.length
                if (abs(distance) < abs(minDistance)) {
                    minDistance = distance
                    param = max(1.0f, ((point - control[0]) dot bc) / (bc dot bc))
                }

                val a = br dot br
                val b = 3.0 * (ab dot br)
                val c = (2.0 * (ab dot ab)) + (qa dot br)
                val d = qa dot ab
                val ts = solveCubic(a.toDouble(), b, c, d.toDouble())

                for (t in ts) {
                    if (t > 0 && t < 1) {
                        val endpoint = position(t.toFloat())
                        val distance2 = sign(ac cross (endpoint - point)) * (endpoint - point).length
                        if (abs(distance2) < abs(minDistance)) {
                            minDistance = distance2
                            param = t.toFloat()
                        }
                    }
                }
                param.coerceIn(0.0f, 1.0f)
            }

            SegmentType.CUBIC -> {
                fun sign(n: Double): Double {
                    val s = n.sign
                    return if (s == 0.0) -1.0 else s
                }

                val qa = start - point
                val ab = control[0] - start
                val bc = control[1] - control[0]
                val cd = end - control[1]
                val qd = end - point
                val br = bc - ab
                val ax = (cd - bc) - br

                var minDistance = sign(ab cross qa) * qa.length
                var param = -(qa dot ab) / (ab dot ab)

                var distance = sign(cd cross qd) * qd.length
                if (abs(distance) < abs(minDistance)) {
                    minDistance = distance
                    param = max(1.0f, (point - control[1] dot cd) / (cd dot cd))
                }
                val searchStarts = 4
                val searchSteps = 8

                for (i in 0 until searchStarts) {
                    var t = i / (searchStarts - 1) + zero
                    var step = 0
                    while (true) {
                        val qpt = position(t) - point
                        distance = sign(direction(t) cross qpt) * qpt.length
                        if (abs(distance) < abs(minDistance)) {
                            minDistance = distance
                            param = t
                        }
                        if (step == searchSteps) {
                            break
                        }
                        val d1 = (ax * (3 * t * t)) + br * (6 * t) + ab * 3.0
                        val d2 = (ax * (6 * t)) + br * 6.0
                        val dt = (qpt dot d1) / ((d1 dot d1) + (qpt dot d2))
                        if (abs(dt) < 1e-14) {
                            break
                        }
                        t -= dt
                        if (t < 0 || t > 1) {
                            break
                        }
                        step++
                    }
                }
                param.coerceIn(0.0f, 1.0f)
            }
        }
        val closest = position(t)
        return SegmentPoint(this, t.toDouble(), closest)
    }

    override fun position(ut: Float): Vec2f {
        val t = ut.coerceIn(0.0f, 1.0f)
        return when (control.size) {
            0 -> Vec2f(
                start.x * (1.0 - t) + end.x * t,
                start.y * (1.0 - t) + end.y * t
            )

            1 -> bezier(start, control[0], end, t.toDouble())
            2 -> bezier(start, control[0], control[1], end, t.toDouble())
            else -> error("unsupported number of control points")
        }
    }

    /**
     * Returns the [t](https://pomax.github.io/bezierinfo/#explanation)
     * values of the extrema for the current [Segment2D].
     *
     * Either one or two *t* values in which the curve
     * is the most distant from an imaginary
     * straight line between the two anchor points.
     */
    fun extrema(): List<Float> {
        val dPoints = dPoints()
        return when {
            linear -> emptyList()
            control.size == 1 -> {
                val xRoots = roots(dPoints[0].map { it.x })
                val yRoots = roots(dPoints[0].map { it.y })
                (xRoots + yRoots).distinct().sorted().filter { it in 0.0f..1.0f }
            }

            control.size == 2 -> {
                val xRoots = roots(dPoints[0].map { it.x }) + roots(dPoints[1].map { it.x })
                val yRoots = roots(dPoints[0].map { it.y }) + roots(dPoints[1].map { it.y })
                (xRoots + yRoots).distinct().sorted().filter { it in 0.0f..1.0f }
            }

            else -> throw RuntimeException("not supported")
        }
    }

    /** Returns the extrema points as [Vec2f]s for current [Segment2D] */
    @Suppress("unused")
    fun extremaPoints(): List<Vec2f> = extrema().map { position(it) }


    private fun dPoints(): List<List<Vec2f>> {
        val points = listOf(start) + control + listOf(end)
        var d = points.size
        var c = d - 1
        val dPoints = mutableListOf<List<Vec2f>>()
        var p = points
        while (d > 1) {
            val list = mutableListOf<Vec2f>()
            for (j in 0 until c) {
                list.add(
                    Vec2f(
                        c * (p[j + 1].x - p[j].x),
                        c * (p[j + 1].y - p[j].y)
                    )
                )
            }
            dPoints.add(list)
            p = list
            d--
            c--
        }
        return dPoints
    }


    private fun angle(o: Vec2f, v1: Vec2f, v2: Vec2f): Float {
        val dx1 = v1.x - o.x
        val dy1 = v1.y - o.y
        val dx2 = v2.x - o.x
        val dy2 = v2.y - o.y
        val cross = dx1 * dy2 - dy1 * dx2
        val dot = dx1 * dx2 + dy1 * dy2
        return atan2(cross, dot)
    }

    /**
     * Determines if the [Segment2D] forms a straight line.
     *
     * If the given [Segment2D] has control points,
     * the function verifies that they do not add any curvature to the path.
     *
     * @param tolerance The margin of error for what's considered a straight line.
     */
    @Suppress("unused")
    fun isStraight(tolerance: Double = 0.01): Boolean {
        return when (control.size) {
            2 -> {
                val dl = (end - start).normalized
                val d0 = (control[0] - start).normalized
                val d1 = (end - control[1]).normalized

                val dp0 = dl.dot(d0)
                val dp1 = (-dl).dot(d1)

                dp0 * dp0 + dp1 * dp1 > (2.0 - 2 * tolerance)
            }

            1 -> {
                val dl = (end - start).normalized
                val d0 = (control[0] - start).normalized

                val dp0 = dl.dot(d0)
                dp0 * dp0 > (1.0 - tolerance)
            }

            else -> {
                true
            }
        }

    }

    override val simple: Boolean
        get() {
            if (linear) {
                return true
            }
            if (control.size == 2) {
                val a1 = angle(start, end, control[0])
                val a2 = angle(start, end, control[1])

                if ((a1 > 0 && a2 < 0) || (a1 < 0 && a2 > 0))
                    return false
            }
            val n1 = normal(0.0f, YPolarity.CW_NEGATIVE_Y)
            val n2 = normal(1.0f, YPolarity.CW_NEGATIVE_Y)
            val s = n1 dot n2
            return s >= 0.9
        }


    val clockwise
        get() = angle(start, end, control[0]) > 0

    /** Converts the [Segment2D] to a cubic Bézier curve. */
     val cubic: Segment2D
        get() = when {
            control.size == 2 -> this
            control.size == 1 -> {
                Segment2D(
                    start,
                    start * (1.0 / 3.0) + control[0] * (2.0 / 3.0),
                    control[0] * (2.0 / 3.0) + end * (1.0 / 3.0),
                    end,
                    corner
                )
            }

            linear -> {
                val delta = end - start
                Segment2D(
                    start,
                    start + delta * (1.0 / 3.0),
                    start + delta * (2.0 / 3.0),
                    end,
                    corner
                )
            }

            else -> error("cannot convert to cubic segment")
        }


    /** Converts the [Segment2D] to a quadratic Bézier curve. */
    val quadratic: Segment2D
        get() = when {
            control.size == 1 -> this
            linear -> {
                val delta = end - start
                Segment2D(start, start + delta * (1.0 / 2.0), end, corner)
            }

            else -> error("cannot convert to quadratic segment")
        }


    override fun derivative(t: Float): Vec2f = when {
        linear -> end - start
        control.size == 1 -> safeDerivative(start, control[0], end, t.toDouble())
        control.size == 2 -> safeDerivative(
            start,
            control[0],
            control[1],
            end,
            t.toDouble()
        )

        else -> throw RuntimeException("not implemented")
    }

    override fun derivative2(t: Float): Vec2f = when {
        linear -> Vec2f.ZERO
        control.size == 1 -> cubic.derivative2(t)
        control.size == 2 -> derivative2(
            start,
            control[0],
            control[1],
            end,
            t.toDouble()
        )

        else -> throw RuntimeException("not implemented")
    }

    override fun curvature(t: Float): Float {
        val d = derivative(t)
        val dd = derivative2(t)
        val numerator = d.cross(dd)
        val denominator = d.length.pow(3.0f)
        return numerator/denominator
    }

    /**
     * Returns a normal [Vec2f] at given value of
     * [t](https://pomax.github.io/bezierinfo/#explanation)
     * in the range of `0.0` to `1.0`.
     */
    fun normal(ut: Float, polarity: YPolarity = YPolarity.CW_NEGATIVE_Y): Vec2f {
        return direction(ut).perpendicular(polarity)
    }

    /** Reverses the order of control points of the given path [Segment2D]. */
    override val reverse: Segment2D
        get() {
            return when (control.size) {
                0 -> Segment2D(end, start)
                1 -> Segment2D(end, control[0], start)
                2 -> Segment2D(end, control[1], control[0], start)
                else -> throw RuntimeException("unsupported number of control points")
            }
        }


    override fun split(t: Double): Array<Segment2D> {
        val u = t.clamp(0.0, 1.0)
        val splitSigma = 10E-6

        if (u < splitSigma) {
            return arrayOf(Segment2D(start, start), this)
        }

        if (u >= 1.0 - splitSigma) {
            return arrayOf(this, Segment2D(end, end))
        }

        if (linear) {
            val cut = start + (end.minus(start) * u)
            return arrayOf(Segment2D(start, cut), Segment2D(cut, end))
        } else {
            when (control.size) {
                2 -> {
                    @Suppress("UnnecessaryVariable") val z = u
                    val z2 = z * z
                    val z3 = z * z * z
                    val iz = 1 - z
                    val iz2 = iz * iz
                    val iz3 = iz * iz * iz

                    val lsm = Mat4(
                        1.0, 0.0, 0.0, 0.0,
                        iz, z, 0.0, 0.0,
                        iz2, 2.0 * iz * z, z2, 0.0,
                        iz3, 3.0 * iz2 * z, 3.0 * iz * z2, z3
                    )

                    val px = Vec4(start.x, control[0].x, control[1].x, end.x)
                    val py = Vec4(start.y, control[0].y, control[1].y, end.y)

                    val plx = lsm * px//.multiply(lsm)
                    val ply = lsm * py// py.multiply(lsm)

                    val pl0 = Vec2f(plx.x, ply.x)
                    val pl1 = Vec2f(plx.y, ply.y)
                    val pl2 = Vec2f(plx.z, ply.z)
                    val pl3 = Vec2f(plx.w, ply.w)

                    val left = Segment2D(pl0, pl1, pl2, pl3)

                    val rsm = Mat4(
                        iz3, 3.0 * iz2 * z, 3.0 * iz * z2, z3,
                        0.0, iz2, 2.0 * iz * z, z2,
                        0.0, 0.0, iz, z,
                        0.0, 0.0, 0.0, 1.0
                    )

                    val prx = rsm * px
                    val pry = rsm * py

                    val pr0 = Vec2f(prx.x, pry.x)
                    val pr1 = Vec2f(prx.y, pry.y)
                    val pr2 = Vec2f(prx.z, pry.z)
                    val pr3 = Vec2f(prx.w, pry.w)

                    val right = Segment2D(pr0, pr1, pr2, pr3)

                    return arrayOf(left, right)
                }

                1 -> {
                    @Suppress("UnnecessaryVariable") val z = u
                    val iz = 1 - z
                    val iz2 = iz * iz
                    val z2 = z * z

                    val lsm = Mat4(
                        1.0, 0.0, 0.0, 0.0,
                        iz, z, 0.0, 0.0,
                        iz2, 2.0 * iz * z, z2, 0.0,
                        0.0, 0.0, 0.0, 0.0
                    )

                    val px = Vec4(start.x, control[0].x, end.x, 0.0)
                    val py = Vec4(start.y, control[0].y, end.y, 0.0)

                    val plx = lsm * px
                    val ply = lsm * py

                    val left = Segment2D(
                        Vec2f(plx.x, ply.x),
                        Vec2f(plx.y, ply.y),
                        Vec2f(plx.z, ply.z)
                    )

                    val rsm = Mat4(
                        iz2, 2.0 * iz * z, z2, 0.0,
                        0.0, iz, z, 0.0,
                        0.0, 0.0, 1.0, 0.0,
                        0.0, 0.0, 0.0, 0.0
                    )

                    val prx = rsm * px
                    val pry = rsm * py

                    val rdx0 = prx.y - prx.x
                    val rdy0 = pry.y - pry.x

                    val rdx1 = prx.z - prx.y
                    val rdy1 = pry.z - pry.y


                    require(rdx0 * rdx0 + rdy0 * rdy0 > 0.0) {
                        "Q start/c0 overlap after split on $t $this"
                    }
                    require(rdx1 * rdx1 + rdy1 * rdy1 > 0.0) {
                        "Q end/c0 overlap after split on $t $this"
                    }

                    val right = Segment2D(
                        Vec2f(prx.x, pry.x),
                        Vec2f(prx.y, pry.y),
                        Vec2f(prx.z, pry.z)
                    )

                    return arrayOf(left, right)
                }

                else -> error("unsupported number of control points")
            }
        }
    }

    override fun toString(): String {
        return "Segment(start=$start, end=$end, control=${control})"
    }


    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null) return false
        if (this::class != other::class) return false

        other as Segment2D

        if (start != other.start) return false
        if (end != other.end) return false
        return control == other.control
    }

    override fun hashCode(): Int {
        var result = start.hashCode()
        result = 31 * result + end.hashCode()
        result = 31 * result + control.hashCode()
        return result
    }

    operator fun times(scale: Double): Segment2D {
        return when (type) {
            SegmentType.LINEAR -> Segment2D(start * scale, end * scale)
            SegmentType.QUADRATIC -> Segment2D(
                start * scale,
                control[0] * scale,
                end * scale
            )

            SegmentType.CUBIC -> Segment2D(
                start * scale,
                control[0] * scale,
                control[1] * scale,
                end * scale
            )
        }
    }

    operator fun div(scale: Double): Segment2D {
        return when (type) {
            SegmentType.LINEAR -> Segment2D(start / scale, end / scale)
            SegmentType.QUADRATIC -> Segment2D(
                start / scale,
                control[0] / scale,
                end / scale
            )

            SegmentType.CUBIC -> Segment2D(
                start / scale,
                control[0] / scale,
                control[1] / scale,
                end / scale
            )
        }
    }

    operator fun minus(right: Segment2D): Segment2D {
        return if (this.type == right.type) {
            when (type) {
                SegmentType.LINEAR -> Segment2D(
                    start - right.start,
                    end - right.end
                )

                SegmentType.QUADRATIC -> Segment2D(
                    start - right.start,
                    control[0] - right.control[0],
                    end - right.end
                )

                SegmentType.CUBIC -> Segment2D(
                    start - right.start,
                    control[0] - right.control[0],
                    control[1] - right.control[1],
                    end - right.end
                )
            }
        } else {
            if (this.type.ordinal > right.type.ordinal) {
                when (type) {
                    SegmentType.LINEAR -> error("impossible?")
                    SegmentType.QUADRATIC -> this - right.quadratic
                    SegmentType.CUBIC -> this - right.cubic
                }
            } else {
                when (right.type) {
                    SegmentType.LINEAR -> error("impossible?")
                    SegmentType.QUADRATIC -> this.quadratic - right
                    SegmentType.CUBIC -> this.cubic - right
                }
            }
        }
    }

    operator fun plus(right: Segment2D): Segment2D {
        return if (this.type == right.type) {
            when (type) {
                SegmentType.LINEAR -> Segment2D(
                    start + right.start,
                    end + right.end
                )

                SegmentType.QUADRATIC -> Segment2D(
                    start + right.start,
                    control[0] + right.control[0],
                    end + right.end
                )

                SegmentType.CUBIC -> Segment2D(
                    start + right.start,
                    control[0] + right.control[0],
                    control[1] + right.control[1],
                    end + right.end
                )
            }
        } else {
            if (this.type.ordinal > right.type.ordinal) {
                when (type) {
                    SegmentType.LINEAR -> error("impossible?")
                    SegmentType.QUADRATIC -> this + right.quadratic
                    SegmentType.CUBIC -> this + right.cubic
                }
            } else {
                when (right.type) {
                    SegmentType.LINEAR -> error("impossible?")
                    SegmentType.QUADRATIC -> this.quadratic + right
                    SegmentType.CUBIC -> this.cubic + right
                }
            }
        }
    }


    override fun signedArea(): Float {
        return when (type) {
            SegmentType.LINEAR -> (start.x * end.y - end.x * start.y) / 2
            SegmentType.QUADRATIC -> {
                val p0 = start
                val p1 =control[0]
                val p2 = end
                return (p2.x * (p0.y - 2 * p1.y)
                        + 2 * p1.x * (p2.y - p0.y)
                        + p0.x * (2 * p1.y + p2.y)) / 6
            }
            SegmentType.CUBIC -> {
                val p0 = start
                val p1 =control[0]
                val p2 =control[1]
                val p3 = end
                return  ((p3.x * (-p0.y - 3 * p1.y - 6 * p2.y)
                        - 3 * p2.x * (p0.y + p1.y - 2 * p3.y)) + 3 * p1.x * (-2 * p0.y + p2.y + p3.y)
                        + p0.x * (6 * p1.y + 3 * p2.y + p3.y)) / 20
              }
            }
    }

    /**
     * Applies given linear transformation.
     */
    fun transform(transform: Mat4f): Segment2D {
        return if (transform === Mat4f.IDENTITY) {
            this
        } else {
            val tStart = (transform * (start.xy01)).div.xy
            val tEnd = (transform * (end.xy01)).div.xy
            val tControl = when (control.size) {
                2 -> listOf((transform * control[0].xy01).div.xy, (transform * control[1].xy01).div.xy)
                1 -> listOf((transform * control[0].xy01).div.xy)
                else -> emptyList()
            }
            copy(start = Vec2f(tStart), control = tControl as List<Vec2f>, end = Vec2f(tEnd))
        }
    }

    override val contour: ShapeContour
        get() = ShapeContour(listOf(this), false)
}


/**
 * Linear segment constructor.
 *
 * @param start The starting anchor point.
 * @param end The ending anchor point.
 */

fun Segment2D(start: Vec2f, end: Vec2f, corner: Boolean = true) =
    Segment2D(start, emptyList(), end, corner)

/**
 * Quadratic Bézier segment constructor.
 *
 * @param start The starting anchor point.
 * @param c0 The control point.
 * @param end The ending anchor point.
 */
fun Segment2D(start: Vec2f, c0: Vec2f, end: Vec2f, corner: Boolean = true) =
    Segment2D(start, listOf(c0), end, corner)

/**
 * Cubic Bézier segment constructor.
 *
 * @param start The starting anchor point.
 * @param c0 The first control point.
 * @param c1 The second control point
 * @param end The ending anchor point.
 */
fun Segment2D(start: Vec2f, c0: Vec2f, c1: Vec2f, end: Vec2f, corner: Boolean = true) =
    Segment2D(start, listOf(c0, c1), end, corner)

/**
 * Calculates a vector perpendicular to the current one.
 *
 * @param polarity The polarity of the new vector, default is [CW_NEGATIVE_Y][YPolarity.CW_NEGATIVE_Y].
 */
fun<N:Number,V: Vector2<N, V>> V.perpendicular(polarity: YPolarity = YPolarity.CW_NEGATIVE_Y) = when (polarity) {
    YPolarity.CCW_POSITIVE_Y -> create(-y, x)
    YPolarity.CW_NEGATIVE_Y -> create(y, -x)
}

internal fun Segment2D.toCurve2(): CurveShape2d {
    return when (control.size) {
        0 -> CurveLine.line(start, end)
        1 -> Bezier2.curve(start, control[0], end)
        2 -> Bezier2.curve(start, control[0], control[1], end)
        else -> throw IllegalArgumentException("unsupported control count ${control.size}")
    }
}

internal fun CurveShape2d.toSegment(): Segment2D {
    return when (this) {
        is CurveLine -> Segment2D(this.start(), this.end())
        is QuadraticBezier2 -> Segment2D(this.p0, this.p1, this.p2)
        is CubicBezier2 -> Segment2D(
            this.p0, this.p1,
            this.p2, this.p3
        )
        else -> throw IllegalArgumentException()
    }
}